package uk.co.hs.dao.generic;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Projections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

import uk.co.hs.domain.generic.UtilisoftGenericDomain;


/**
 * UtilisoftGenericDao.
 *
 * @param <T> Class type.
 * @param <ID> ID Class type.
 */
@Repository
public abstract class UtilisoftGenericDao<T, ID extends Serializable>
{
  @Autowired(required = true)
  @Qualifier(value = "hs_sessionFactory")
  protected SessionFactory mSessionFactory;

  @Value("${hs.nullCheck:false}") //default to false if non provided
  private Boolean mNullCheck;

  @Value("${hs.nullCheckTrace:false}")
  private Boolean mNullCheckTrace;

  protected Class<T> mClassType;

  /**
   * Constructor.
   */
  @SuppressWarnings("unchecked")
  public UtilisoftGenericDao()
  {
    Class<?> clazz = getClass();
    while (!(clazz.getGenericSuperclass() instanceof ParameterizedType))
    {
      clazz = clazz.getSuperclass();
    }
    mClassType = (Class<T>) ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0];
  }

  /**
   * Get object T by ID.
   * @param aId ID.
   * @return Object T.
   */
  @SuppressWarnings("unchecked")
  public T getById(final ID aId)
  {
    return (T) getSession().get(getClassType(), aId);
  }

  /**
   * Get all objects of type T.
   * @return List of Object Ts.
   */
  @SuppressWarnings("unchecked")
  public List<T> getAll()
  {
    return (List<T>) getSession().createQuery("from " + getClassName()).list();
  }

  /**
   * Removes all objects of type T from the database.
   */
  public void makeAllTransient()
  {
    batchMakeTransient(getAll());
  }

  /**
   * Persist T to the database.
   * @param aEntity T to save.
   * @return T.
   */
  @SuppressWarnings("unchecked")
  public void makePersistent(final T aEntity)
  {
  	if(mNullCheck)
  	{
  		jpaValidate(aEntity);
  	}

    if (((UtilisoftGenericDomain<Number>) aEntity).getPk() == null)
    {
      getSession().save(aEntity);
    }
    else
    {
      getSession().update(aEntity);
    }
  }


  @SuppressWarnings("unchecked")
  public void jpaValidate(final T aEntity)
  {
  	((UtilisoftGenericDomain<Number>) aEntity).performNonNullableCheck(mNullCheckTrace);
  }

  /**
   * Delete T from the database.
   * @param aEntity T to delete.
   */
  public void makeTransient(T aEntity)
  {
    getSession().delete(aEntity);
  }

  /**
   * Persist List of Ts to the database.
   * @param aEntities List of Ts to save.
   * @return Ts.
   */
  @SuppressWarnings("unchecked")
  public void batchMakePersistent(final List<T> aEntities)
  {
    Session session = getSession();

    int size = aEntities.size();
    for (int i = 0; i < size; i++)
    {
    	T entity = aEntities.get(i);
      if (((UtilisoftGenericDomain<Number>) aEntities.get(i)).getPk() == null)
      {
        session.persist(entity);
      }
      else
      {
        session.update(entity);
      }

      if (i != 0 && i % 50 == 0)
      {
        session.flush();
        session.clear();
      }
    }
  }

  /**
   * Delete Ts from the database.
   * @param aEntities Ts to delete.
   */
  public void batchMakeTransient(final List<T> aEntities)
  {
    Session session = getSession();

    int size = aEntities.size();
    for (int i = 0; i < size; i++)
    {
      session.delete(aEntities.get(i));

      if (i != 0 && i % 50 == 0)
      {
        session.flush();
        session.clear();
      }
    }
  }

  /**
   * Count of Ts.
   * @return Count of T objects in the database.
   */
  public Long getAllCount()
  {
    return (Long) getSession().createQuery("SELECT COUNT(*) FROM " + getClassName()).uniqueResult();
  }

  /**
   * @param aSessionFactory the sessionFactory to set
   */
  public void setSessionFactory(SessionFactory aSessionFactory)
  {
    mSessionFactory = aSessionFactory;
  }

  protected Session getSession()
  {
    return mSessionFactory.getCurrentSession();
  }

  protected String getClassName()
  {
    return mClassType.getSimpleName();
  }


  /**
   * @return Class Type.
   */
  private Class<T> getClassType()
  {
    return mClassType;
  }


  protected Long getCount(Criteria aCriteria)
  {
  	return (Long) aCriteria.setProjection(Projections.rowCount())
								.uniqueResult();
  }
}
